import os
import subprocess
import unittest
import sys
import logging
import tempfile
import shutil
from typing import List, Dict, Any, Optional

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler('test_keels_cli.log')
    ]
)
logger = logging.getLogger(__name__)

DEVICE_IDS = {
    "android_device": os.environ.get("ANDROID_DEVICE_ID", ""),
    "android_emulator": os.environ.get("ANDROID_EMULATOR_ID", ""),
    "harmony_device": os.environ.get("HARMONY_DEVICE_ID", ""),
    "harmony_emulator": os.environ.get("HARMONY_EMULATOR_ID", ""),
    "ios_device": os.environ.get("IOS_DEVICE_ID", ""),
    "ios_simulator": os.environ.get("IOS_SIMULATOR_ID", "")
}

class TestKeelsCLI(unittest.TestCase):
    def setUp(self):
        self.original_cwd = os.getcwd()
        self.test_counter = 0
        self.temp_dir = tempfile.mkdtemp(prefix="keels_test_")
        os.chdir(self.temp_dir)
        self.current_test_dir = None
        logger.info(f"Original directory: {self.original_cwd}")
        logger.info(f"Temp test directory: {self.temp_dir}")

    def tearDown(self):
        os.chdir(self.original_cwd)
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)
        logger.info("Test completed, temp directory cleaned")

    def run_command(self, command, check=True, cwd=None):
        try:
            current_dir = cwd or os.getcwd()
            logger.info(f"Execute command: {command}")
            logger.info(f"Current directory: {current_dir}")

            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                cwd=cwd
            )

            if result.returncode == 0:
                logger.info(f"Command return code: {result.returncode}")
            else:
                logger.error(f"Command return code: {result.returncode}")

            all_output = result.stdout + result.stderr
            if all_output.strip():
                logger.info(f"Command output: {all_output}")

            if check and result.returncode != 0:
                error_msg = f"Command failed: {command}\nReturn code: {result.returncode}\nOutput: {all_output}"
                logger.error(error_msg)
                self.fail(error_msg)

            return result

        except Exception as e:
            error_msg = f"Command execution error: {command}\nError: {str(e)}"
            logger.error(error_msg)
            if check:
                self.fail(error_msg)
            return None

    def create_project(self, project_type, project_name):
        logger.info(f"Creating project: {project_type} - {project_name}")

        if project_type == "app":
            create_cmd = f"keels create --app {project_name}"
        elif project_type == "package-keels":
            create_cmd = f"keels create --package {project_name} --app-type keels"
        elif project_type == "package-native":
            create_cmd = f"keels create --package {project_name} --app-type native"
        elif project_type == "module":
            create_cmd = f"keels create --module {project_name}"
        else:
            logger.warning(f"Unknown project type: {project_type}")
            return False

        result = self.run_command(create_cmd, check=False)
        if result.returncode != 0:
            logger.warning(f"Failed to create {project_type}")
            return False

        if not os.path.exists(project_name):
            logger.warning(f"Project directory not created: {project_name}")
            return False

        logger.info(f"Project created: {project_name}")
        self.current_test_dir = os.path.join(self.temp_dir, project_name)
        return True

    def build_project(self, product, compile_type, project_dir=None):
        logger.info(f"Building project: {product} - {compile_type}")

        build_dir = project_dir or self.current_test_dir or self.temp_dir
        build_cmd = f"keels build {product} --{compile_type}"

        result = self.run_command(build_cmd, check=False, cwd=build_dir)
        build_success = result.returncode == 0

        if build_success:
            logger.info(f"Build successful: {product} - {compile_type}")
        else:
            logger.warning(f"Build failed: {product} - {compile_type}")

        return build_success

    def run_project(self, product, compile_type, project_dir=None):
        logger.info(f"Running project: {product} - {compile_type}")

        run_dir = project_dir or self.current_test_dir or self.temp_dir

        device_id = ""
        if product in ["apk", "aar"]:
            device_id = DEVICE_IDS["android_device"] or DEVICE_IDS["android_emulator"]
        elif product in ["hap", "har"]:
            device_id = DEVICE_IDS["harmony_device"] or DEVICE_IDS["harmony_emulator"]
        elif product in ["ios", "ipa", "framework"]:
            device_id = DEVICE_IDS["ios_device"]
        elif product in ["ios-sim", "framework-sim"]:
            device_id = DEVICE_IDS["ios_simulator"]

        if device_id:
            run_cmd = f"keels run --{compile_type} -d {device_id}"
        else:
            run_cmd = f"keels run --{compile_type}"

        result = self.run_command(run_cmd, check=False, cwd=run_dir)
        run_success = result.returncode == 0

        if run_success:
            logger.info(f"Run successful: {product} - {compile_type}")
        else:
            logger.warning(f"Run failed: {product} - {compile_type}")

        return run_success

    def verify_build_output(self, platform, project_type, target_platform, product, compile_type, project_dir=None):
        logger.info(f"Verifying build output: {platform} - {target_platform} - {product}")

        build_dir = project_dir or self.temp_dir
        verification_passed = True

        if platform == "MacOS" and target_platform == "iOS":
            verification_passed = self._verify_ios_build(project_type, product, compile_type, build_dir)
        elif target_platform == "Android":
            verification_passed = self._verify_android_build(project_type, product, compile_type, build_dir)
        elif target_platform == "OHOS":
            verification_passed = self._verify_ohos_build(project_type, product, compile_type, build_dir)

        return verification_passed

    def _verify_ios_build(self, project_type, product, compile_type, build_dir):
        sdk_type = "iphonesimulator" if product in ["ios-sim", "framework-sim"] else "iphoneos"

        if project_type in ["app", "module"]:
            build_output_dir = os.path.join("ios", "output", "DerivedData", "Build", "Products", f"{compile_type.capitalize()}-{sdk_type}")
        elif project_type in ["package-keels", "package-native"]:
            project_name = os.path.split(build_dir)[-1]
            build_output_dir = os.path.join("ios", project_name, "output", "Build", "Products", f"{compile_type.capitalize()}-{sdk_type}")
        else:
            logger.warning(f"Unknown project type: {project_type}")
            return False

        full_build_path = os.path.join(build_dir, build_output_dir)

        if not os.path.exists(full_build_path):
            logger.warning(f"iOS build directory not found: {build_output_dir}")
            return False

        logger.info(f"iOS build directory exists: {build_output_dir}")

        build_files = os.listdir(full_build_path)
        if not build_files:
            logger.warning(f"iOS build directory empty: {build_output_dir}")
            return False

        logger.info(f"iOS build files: {build_files}")
        return True

    def _verify_android_build(self, project_type, product, compile_type, build_dir):
        return True

    def _verify_ohos_build(self, project_type, product, compile_type, build_dir):
        return True

    def execute_test_combination(self, combination):
        self.test_counter += 1

        platform = combination['platform']
        project_type = combination['project_type']
        target_platform = combination['target_platform']
        compile_type = combination['compile_type']
        product = combination['product']

        platform_abbr = {"Windows": "win", "MacOS": "mac"}.get(platform, platform[:3])
        project_type_abbr = {"app": "app", "package-keels": "pkgk", "package-native": "pkgn", "module": "mod"}.get(project_type, project_type[:4])
        target_platform_abbr = {"Android": "and", "iOS": "ios", "OHOS": "ohos"}.get(target_platform, target_platform[:3])
        compile_type_abbr = compile_type[:3]
        product_abbr = {"apk": "apk", "aar": "aar", "hap": "hap", "har": "har", "ios": "ios", "ipa": "ipa", "ios-sim": "sim", "framework": "fw", "framework-sim": "fws"}.get(product, product[:3])

        test_name = f"{platform_abbr}_{project_type_abbr}_{target_platform_abbr}_{product_abbr}_{compile_type_abbr}".lower()
        logger.info(f"=== Starting test {self.test_counter}: {platform} {project_type} {target_platform} {compile_type} {product} ===")

        if not self.create_project(project_type, test_name):
            self.skipTest(f"Failed to create {project_type}")
            return

        build_success = self.build_project(product, compile_type)
        verification_passed = self.verify_build_output(platform, project_type, target_platform, product, compile_type, self.current_test_dir)
        run_success = self.run_project(product, compile_type)

        if verification_passed and run_success:
            logger.info(f"Test {self.test_counter} completed successfully")
        else:
            logger.warning(f"Test {self.test_counter} failed - Build: {build_success} Verify: {verification_passed} Run: {run_success}")
            self.fail(f"Test failed - Build: {build_success}")

class ParameterDictionary:
    PLATFORMS = {
        "Windows": ["Android", "OHOS"],
        "MacOS": ["iOS", "Android", "OHOS"]
    }

    PROJECT_TYPE = {
        "app": ["apk", "ios", "ios-sim", "hap", "ipa"],
        "package-keels": ["aar", "har", "framework", "framework-sim"],
        "package-native": ["aar", "har", "framework", "framework-sim"],
        "module": ["aar", "har", "framework", "framework-sim"]
    }

    TARGET_PLATFORMS = {
        "Android": ["apk", "aar"],
        "iOS": ["ios", "ipa", "framework", "ios-sim", "framework-sim"],
        "OHOS": ["hap", "har"]
    }

    COMPILE_TYPE = ["debug", "release"]

    @classmethod
    def generate_combinations(cls, platforms=None, target_platforms=None):
        if target_platforms and not platforms:
            raise ValueError("Platforms must be specified before target_platforms")

        selected_platforms = platforms or list(cls.PLATFORMS.keys())

        if target_platforms:
            invalid_platforms = [p for p in platforms if p not in cls.PLATFORMS]
            if invalid_platforms:
                raise ValueError(f"Invalid platforms: {invalid_platforms}")

        if target_platforms is None:
            all_targets = set()
            for platform in selected_platforms:
                all_targets.update(cls.PLATFORMS[platform])
            selected_target_platforms = list(all_targets)
        else:
            all_valid_targets = set()
            for platform in selected_platforms:
                all_valid_targets.update(cls.PLATFORMS[platform])
            invalid_targets = [t for t in target_platforms if t not in all_valid_targets]
            if invalid_targets:
                raise ValueError(f"Invalid target platforms: {invalid_targets}")
            selected_target_platforms = target_platforms

        combinations = []
        for platform in selected_platforms:
            for target_platform in selected_target_platforms:
                if target_platform in cls.PLATFORMS[platform]:
                    for project_type in cls.PROJECT_TYPE.keys():
                        for compile_type in cls.COMPILE_TYPE:
                            valid_products = set(cls.PROJECT_TYPE[project_type]) & set(cls.TARGET_PLATFORMS[target_platform])
                            for product in valid_products:
                                combinations.append({
                                    "platform": platform,
                                    "project_type": project_type,
                                    "target_platform": target_platform,
                                    "product": product,
                                    "compile_type": compile_type
                                })
        return combinations

    @classmethod
    def get_filtered_combinations(cls, platforms=None, target_platforms=None, project_types=None, compile_types=None, products=None):
        combinations = cls.generate_combinations(platforms, target_platforms)
        filtered_combinations = []
        for combo in combinations:
            if project_types and combo['project_type'] not in project_types:
                continue
            if compile_types and combo['compile_type'] not in compile_types:
                continue
            if products and combo['product'] not in products:
                continue
            filtered_combinations.append(combo)
        return filtered_combinations

def create_dynamic_test_method(combo):
    def test_method(self):
        return self.execute_test_combination(combo)
    return test_method

def create_dynamic_tests():
    combinations = ParameterDictionary.get_filtered_combinations(
        platforms=["MacOS"],
        target_platforms=["iOS"],
        project_types=["app", "package-keels", "module"],
        compile_types=["debug"],
        products=["ios", "ios-sim", "framework", "framework-sim"]
    )

    for i, combination in enumerate(combinations, 1):
        platform = combination['platform']
        project_type = combination['project_type']
        target_platform = combination['target_platform']
        compile_type = combination['compile_type']
        product = combination['product']

        test_name = f"{platform}_{project_type}_{target_platform}_{product}_{compile_type}".lower()
        method_name = f"test_{i:03d}_{test_name}"

        test_method = create_dynamic_test_method(combination)
        setattr(TestKeelsCLI, method_name, test_method)

if __name__ == "__main__":
    logger.info("Device configuration:")
    for device_type, device_id in DEVICE_IDS.items():
        status = "Configured" if device_id else "Not configured"
        logger.info(f"  {device_type}: {device_id} ({status})")

    create_dynamic_tests()
    unittest.main(verbosity=2)